﻿using System;
using System.Runtime.InteropServices;

namespace GLDotNet
{
    public class VertexArray : GLObject
    {
        public VertexArray(GraphicsDevice graphicsDevice)
            : base(graphicsDevice)
        {
            uint vertexArray = 0;
            this.Context.GenVertexArrays(1, out vertexArray);
            this.Handle = vertexArray;
        }

        protected override void DeleteObject()
        {
            uint vertexArray = this.Handle;
            this.Context.DeleteVertexArrays(1, ref vertexArray);
        }

        public void EnableAttribArray(uint index)
        {
            this.GraphicsDevice.BindVertexArray(this.Handle);
            this.Context.EnableVertexAttribArray(index);
        }

        public void DisableAttribArray(uint index)
        {
            this.GraphicsDevice.BindVertexArray(this.Handle);
            this.Context.DisableVertexAttribArray(index);
        }

        private VertexAttribPointerType GetVertexAttribPointerTypeForDataType(Type dataType)
        {
            if (dataType == typeof(byte))
            {
                return VertexAttribPointerType.UnsignedByte;
            }
            else if (dataType == typeof(double))
            {
                return VertexAttribPointerType.Double;
            }
            else if (dataType == typeof(float) ||
                     dataType == typeof(Color3) || dataType == typeof(Color4) ||
                     dataType == typeof(Vector2) || dataType == typeof(Vector3))
            {
                return VertexAttribPointerType.Float;
            }
            else if (dataType == typeof(int))
            {
                return VertexAttribPointerType.Int;
            }
            else if (dataType == typeof(sbyte))
            {
                return VertexAttribPointerType.Byte;
            }
            else if (dataType == typeof(short))
            {
                return VertexAttribPointerType.Short;
            }
            else if (dataType == typeof(uint))
            {
                return VertexAttribPointerType.UnsignedInt;
            }
            else if (dataType == typeof(ushort))
            {
                return VertexAttribPointerType.UnsignedShort;
            }

            throw new InvalidOperationException(string.Format("Unable to determine VertexAttribPointerType for {0}. Please use an overload which specifies a VertexAttribPointerType.", dataType));
        }

        public void SetAttribPointer<T>(VertexBuffer<T> buffer, uint index, bool normalized, int stride, IntPtr offset)
            where T : struct
        {
            Type dataType = typeof(T);
            VertexAttribPointerType type = this.GetVertexAttribPointerTypeForDataType(dataType);
            this.SetAttribPointer(buffer, index, type, Marshal.SizeOf(dataType) / 4, normalized, stride, offset);
        }

        public void SetAttribPointer<T>(VertexBuffer<T> buffer, uint index, int size, bool normalized, int stride, IntPtr offset)
            where T : struct
        {
            Type dataType = typeof(T);
            VertexAttribPointerType type = this.GetVertexAttribPointerTypeForDataType(dataType);
            this.SetAttribPointer(buffer, index, type, size, normalized, stride, offset);
        }

        public void SetAttribPointer<T>(VertexBuffer<T> buffer, uint index, VertexAttribPointerType type, int size, bool normalized, int stride, IntPtr offset)
            where T : struct
        {
            if (buffer == null)
                throw new ArgumentNullException("buffer");

            this.GraphicsDevice.BindVertexArray(this.Handle);
            buffer.Bind();
            this.Context.VertexAttribPointer(index, size, type, normalized, stride, offset);
        }

        public void SetAttribs<T>(VertexBuffer<T> buffer, Type vertexType)
            where T : struct
        {
            this.SetAttribs<T>(buffer, vertexType, false);
        }

        public void SetAttribs<T>(VertexBuffer<T> buffer, Type vertexType, bool normalized)
            where T : struct
        {
            if (buffer == null)
                throw new ArgumentNullException("buffer");

            this.GraphicsDevice.BindVertexArray(this.Handle);
            buffer.Bind();

            uint index = 0;

            foreach (var fieldInfo in vertexType.GetFields())
            {
                this.EnableAttribArray(index);
                VertexAttribPointerType type = this.GetVertexAttribPointerTypeForDataType(fieldInfo.FieldType);
                this.Context.VertexAttribPointer(index, Marshal.SizeOf(fieldInfo.FieldType) / 4, type, normalized, Marshal.SizeOf(vertexType), Marshal.OffsetOf(vertexType, fieldInfo.Name));
                index++;
            }
        }
    }
}
